<?php  if (!defined('BASEPATH')) exit('No direct script access allowed');

require_once APPPATH . 'controllers/main.php';

/**
 * Абстрактный контроллер, реализовывающий авторизицию
 * Общий предок всех контроллеров, использующий авторизацию
 *
 * @package CoreFramework
 * @version 1.0
 * @author Gennadiy Kozlenko
 */
class Authorization extends Main {
   
   /**
    * Информация по авторизованному пользователю в виде ассоциативного массива
    * Содержит следующие поля:
    *    id        - Идентификатор
    *    name      - Логин
    *    real_name - Реальное имя
    *    password  - Пароль (может быть закриптован)
    *    email     - Мыло
    *    status    - Статус пользователя
    *
    * @access protected
    * @var array
    */
   var $_auth_info = array();
   
   /**
    * Название переменной в сессии и куках, содержащих инфу по авторизованному пользователю
    * К тому же определяет папку к шаблонам логина и восстановления пароля
    *
    * @access protected
    * @var atring
    */
   var $_auth_name = 'auth';
   
   /**
    * Функция хеширования пароля
    *
    * @access protected
    * @var string
    */
   var $_hash_function = 'md5';
   
   /**
    * Индекс элемента главного меню
    *
    * @access protected
    * @var integer
    */
   var $_menu_item = 0;
   
   /**
    * Индекс элемента подменю
    *
    * @access protected
    * @var integer
    */
   var $_submenu_item = 0;
   
   /**
    * Конструктор класса
    *
    * @access public
    * @return Authorization
    */
   function Authorization() {
      parent::Main();
      $this->lang->load('authorization');
      // Пытаемся получить инфу по пользователю из сессии
      $this->_auth_info = $this->session->get($this->_auth_name, array());
      if (0 == count($this->_auth_info)) {
         // Пытаемся получить из кук
         $this->load->library('encrypt');
         $cinfo = @unserialize($this->encrypt->decode(get_cookie($this->_auth_name)));
         if (is_array($cinfo) && isset($cinfo['name']) && isset($cinfo['password'])) {
            // Получаем по этому пользователю инфо
            $info = $this->_get_info($cinfo['name']);
            if ($info['password'] == $cinfo['password'] && 'active' == $info['status']) {
               $this->_login($info, true);
            }
         }
      }
   } // end Authorization
   
   /**
    * Авторизован ли пользователь?
    *
    * @access public
    * @return boolean
    */
   function is_loggined() {
      return isset($this->_auth_info['id']) && 0 < $this->_auth_info['id'];
   } // end is_loggined
   
   /**
    * Получение информации по авторизованному пользователю
    * Содержит следующие поля:
    *    id        - Идентификатор
    *    name      - Логин
    *    real_name - Реальное имя
    *    password  - Пароль (может быть закриптован)
    *    email     - Мыло
    *    status    - Статус пользователя
    *
    * @access public
    * @return boolean
    */
   function get_info() {
      return $this->_auth_info;
   } // end get_info
   
   /**
    * Получение идентификатора авторизованного пользователя
    *
    * @access public
    * @return integer
    */
   function get_id() {
      return isset($this->_auth_info['id']) ? (int) $this->_auth_info['id'] : 0;
   } // end get_id
   
   /**
    * Установка индексов меню
    *
    * @access public
    * @param integer $menu_item
    * @param integer $submenu_item
    */
   function set_menu($menu_item, $submenu_item = 0) {
      $this->_menu_item = $menu_item;
      $this->_submenu_item = $submenu_item;
   } // end set_menu
   
   /**
    * Процесс авторизации пользователя
    *
    * @access public
    */
   function login() {
      $this->add_param('title', $this->lang->line('login_title'));
      // Получаем необходимые параметры из поста
      $login = trim($this->input->post('login'));
      $password = trim($this->input->post('password'));
      $remember = $this->input->post('remember') ? true : false;
      if (false !== $this->input->post('auth')) {
         // Была нажата кнопка Login
         // Хэшируем пароль (если нужно)
         if (!empty($this->_hash_function) && is_callable($this->_hash_function)) {
            $password = call_user_func($this->_hash_function, $password);
         }
         // Получаем инфу по пользователю
         $info = $this->_get_info($login);
         // Проверки
         if (empty($login)) {
            // Не ввели логин
            $this->set_message($this->lang->line('login_required'), 'error');
         } elseif (false === $info || 'deleted' == $info['status']) {
            // Пользователь не обнаружен
            $this->set_message($this->lang->line('user_not_exists'), 'error');
         } elseif (!is_array($info['password']) && $password != $info['password']) {
            // Неверный пароль
            $this->set_message($this->lang->line('invalid_password'), 'error');
         } elseif (is_array($info['password']) && !in_array($password, $info['password'])) {
            // Неверный пароль
            $this->set_message($this->lang->line('invalid_password'), 'error');
         } elseif ('unconfirmed' == $info['status']) {
            // Пользователь не подтвержден
            $this->set_message($this->lang->line('user_not_confirmed'), 'error');
         } elseif ('unapproved' == $info['status']) {
            // Пользователь не подтвержден администратором
            $this->set_message($this->lang->line('user_not_confirmed'), 'error');
         } elseif ('paused' == $info['status']) {
            // Пользователь отморожен :-)
            $this->set_message($this->lang->line('user_paused'), 'error');
         } else {
            // Все верно, авторизовываем пользователя
            $this->_login($info, $remember);
         }
      }
      
      $this->set_title($this->lang->line('login_title'));
      $data = array(
         'login'    => htmlspecialchars($login),
         'remember' => (string) $remember
      );
      $this->set_content($this->parser->parse($this->_auth_name . '/login.html', $data, true));
      $this->display();
   } // end login
   
   /**
    * Процесс выходя пользователя из системы
    *
    * @access public
    */
   function logout() {
      $this->_logout();
      $this->redirect($this->get_controller(), 'login');
   } // end logout
   
   /**
    * Процесс восстановления пароля
    *
    * @access public
    */
   function recover() {
      // Получаем необходимые параметры из поста
      $login = trim($this->input->post('login'));
      if (false !== $this->input->post('auth')) {
         // Была нажата кнопка Recover
         // Получаем инфу по пользователю
         $info = $this->_get_info($login);
         // Проверки
         if (empty($login)) {
            // Не ввели логин
            $this->set_message($this->lang->line('login_required'), 'error');
         } elseif (false === $info || 'deleted' == $info['status']) {
            // Пользователь не обнаружен
            $this->set_message($this->lang->line('user_not_exists'), 'error');
         } else {
            // Все верно, восстанавливаем пароль
            $this->_recover($info);
         }
      }
      
      $this->set_title($this->lang->line('recover_title'));
      $data = array(
         'login' => htmlspecialchars($login)
      );
      $this->set_content($this->parser->parse($this->_auth_name . '/recover.html', $data, true));
      $this->display();
   } // end recover
   
   /**
    * Вывод контента в браузер
    *
    * @access public
    */
   function display() {
      // Если нужно, подгружаем пользовательское меню
      if ($this->is_loggined()) {
         $this->add_param('menu', $this->_get_menu());
      }
      parent::display();
   } // end display
   
   /**
    * Непосредственная авторизация пользователя (после всех успешных проверок)
    *
    * @access protected
    * @param array $info
    * @param boolean $remember
    */
   function _login($info, $remember = false) {
      $this->session->set($this->_auth_name, $info);
      if ($remember) {
         $cinfo = array(
            'id'        => $info['id'],
            'name'     => $info['name'],
            'password'  => $info['password'],
            'real_name' => $info['real_name']
         );
         $this->load->library('encrypt');
         set_cookie($this->_auth_name, $this->encrypt->encode(serialize($cinfo)), 30 * 86400);
      } elseif (false !== get_cookie($this->_auth_name)) {
         delete_cookie($this->_auth_name);
      }
      $this->redirect($this->get_controller());
   } // end _login
   
   /**
    * Непосредственный выход пользователя из системы
    *
    * @access protected
    */
   function _logout() {
      $this->session->clean($this->_auth_name);
      delete_cookie($this->_auth_name);
      $this->_auth_info = array();
   } // end _logout
   
   /**
    * Непосредственное восстановление пароля
    *
    * @param array $info
    * @access protected
    */
   function _recover($info) {
      // Получаем новый пароль
      $password = '';
      $cpass = '';
      if (empty($this->_hash_function)) {
         // Пароль в открытом виде. Новый пароль не нужен
         $password = $info['password'];
         $cpass = $password;
      } else {
         // Генерируем новый пароль
         $password = $this->_generate_password();
         if (is_callable($this->_hash_function)) {
            $cpass = call_user_func($this->_hash_function, $password);
         } else {
            $cpass = $password;
         }
         // Апдейтим пароль
         if (!$this->_change_password($info['name'], $cpass)) {
            $this->set_message($this->lang->line('password_not_changed'), 'error');
            return;
         }
      }
      // Получаем контент письма
      $data = array(
         'real_name' => $info['real_name'],
         'name'      => $info['name'],
         'password'  => $password,
         'site_name' => $this->variables->get('site_name'),
         'class'     => $this->get_controller()
      );
      $subject = '';
      $content = '';
      if (get_email($this->_auth_name . '_recover', $subject, $content, $data)) {
      // Высылаем мыло
         $this->load->plugin('phpmailer');
         $mail = new PHPMailer();
         $mail->Mailer = 'mail';
         $mail->CharSet = 'utf-8';
         $mail->ContentType = 'text/html';
         $mail->From = $this->variables->get('site_email');
         $mail->FromName = $this->variables->get('site_name');
         $mail->AddAddress($info['email']);
         $mail->Subject = $subject;
         $mail->Body = $content;
         if ($mail->Send()) {
         $this->set_message($this->lang->line('password_sent_succ'), 'info', true);
      } else {
            $this->set_message($this->lang->line('password_sent_error'), 'error');
         }
      } else {
         $this->set_message($this->lang->line('password_sent_error'), 'error');
      }
   } // end _recover
   
   /**
    * Функция генерирования нового пароля
    *
    * @access protected
    * @return string
    */
   function _generate_password() {
      $this->load->helper('string');
      return random_string('alnum', 8);
   } // end _generate_password
   
   /**
    * Получение информации по пользователю по его логину
    *
    * @access protected
    * @param string $login
    * @return mixed
    */
   function _get_info($login) {
      return false;
   } // end _get_info
   
   /**
    * Изменение пароля пользователю по логину
    *
    * @access protected
    * @param string $login
    * @param string $password
    * @return boolean
    */
   function _change_password($login, $password) {
      return false;
   } // end _change_password
   
   /**
    * Метод получения HTML представления пользовательского меню
    *
    * @access protected
    * @return string
    */
   function _get_menu() {
      $data = array(
         'item_' . $this->_menu_item       => 'active',
         'subitem_' . $this->_submenu_item => 'active',
         'subitems_' . $this->_menu_item   => array(array()),
      );
      $output = $this->parser->parse($this->_auth_name . '/menu.html', $data, true);
      $output = preg_replace('~{%subitems_\d+%}.*?{%/subitems_\d+%}~msi', '', $output);
      $output = preg_replace('~{%(sub)?item_\d+%}~msi', '', $output);
      return $output;
   } // end _get_menu
   
   /**
    * Ремап методов для обеспечения безопасности
    *
    * @access public
    * @param string $method
    */
   function _remap($method) {
      if (!in_array($method, array('login', 'recover'))) {
         if (!$this->is_loggined()) {
            $this->redirect($this->get_controller(), 'login');
         }
      } else {
         $this->_logout();
      }
      if (is_callable(array($this, $method))) {
         call_user_func(array($this, $method));
      } else {
         show_404();
      }
   } // end _remap
   
}
